package org.custom.spring.boot.security.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.custom.spring.boot.security.entity.Login;
import org.custom.spring.boot.security.entity.SystemUser;
import org.custom.spring.boot.security.entity.SystemUserInfo;
import org.custom.spring.boot.security.entity.security.SecurityProperties;
import org.custom.spring.boot.security.exceptions.JwtException;
import org.custom.spring.boot.security.exceptions.TokenExpireException;
import org.custom.spring.boot.security.service.ApproveService;
import org.custom.spring.boot.security.token.TokenManager;
import org.custom.spring.boot.security.utils.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;

import static org.custom.spring.boot.security.constant.SecurityConstant.AUTOMATIC_KEY_EXPIRE;

/**
 * 用户认证相关服务
 *
 * @author LiKun
 * @date 2021/11/10 16:41
 */
@Validated
@Service("approveService")
public class ApproveServiceImpl implements ApproveService {
    private static final Logger logger = LoggerFactory.getLogger(ApproveServiceImpl.class);

    @Autowired
    private TokenManager tokenManager;
    @Resource
    private transient RedisTemplate redisTemplate;
    @Resource
    private SecurityProperties securityProperties;
    @Resource
    private transient AuthenticationManagerBuilder authenticationManagerBuilder;

    @Override
    public String login(Login login, String iP) throws JsonProcessingException {
        // 认证用户账号是否正确
        SystemUser user = this.authenticate(login);
        // 为该用户生成登陆凭证
        String token = tokenManager.generateAndTakeEffectToken(user);

        // 实现用户自动登录功能
        if (securityProperties.isOpenRemember() && login.isRememberMe()) {
            // 生成缓存Key，并将其存放在缓存中
            String automaticKey = SecurityUtil.getAutomaticKey(user.getId(), iP);
            redisTemplate.boundValueOps(automaticKey).set(token, AUTOMATIC_KEY_EXPIRE);
        }
        return token;
    }

    @Override
    public void authenticate(String token) throws JsonProcessingException {
        SystemUserInfo userInfo;
        try {
            // 解析 Token 令牌并获取用户数据
            userInfo = tokenManager.parseTokenSubject(token);
            // 校验 Token 令牌的有效性
            this.checkValidity4TokenKey(userInfo.getTokenKey());
        } catch (JwtException jwtException) {
            logger.info("令牌解析错误");
            jwtException.printStackTrace();
            userInfo = SystemUserInfo.getAnonymous();
        }

        // 获取用于认证的相关数据
        Authentication authentication = userInfo.getAuthentication();
        // 将认证信息存放在 SecurityContext 中
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    /*-------------------------------------------------private methods------------------------------------------------*/

    /**
     * 校验令牌编号的有效性(是否过期)
     *
     * @param tokenKey 令牌编号
     */
    private void checkValidity4TokenKey(String tokenKey) {
        if (!redisTemplate.hasKey(tokenKey)) {
            throw new TokenExpireException();
        }
    }

    /**
     * 根据 登陆数据 认证用户身份信息
     *
     * @param login 登陆数据
     *
     * @return 用户数据
     */
    private SystemUser authenticate(Login login) {
        // 获取待认证实体数据
        Authentication authentication = login.getAuthentication();
        // 调用 Spring-Security 相关认证逻辑
        return (SystemUser) authenticationManagerBuilder.getObject().authenticate(authentication).getPrincipal();
    }
}
